package com.infoblazer.jimql;

import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.*;

/**
 * User: davidm
 * Date: 1/31/11
 * Time: 11:22 AM
 */
public class JimqlMap extends Jimql {

    /**
     * Map implementation does not maintain a list of hashcodes
     *
     * @return
     */
    protected Map<String, Map<Integer, Object>> getHashCodes() {
        return null;
    }

    public JimqlMap(DBTYPE dbType) {

        this.dbType = dbType;
    }

    @Override
    protected int loadColumnValue(PreparedStatement ps, int i, Object tuple, Column column) throws SQLException {
        Map<String, Object> map = (Map<String, Object>) tuple;
       Object object =  map.get(column.columnName);
        return  super.loadColumnValueObject( ps,  i,    column,object);


    }

    @Override
    protected Relation findColumns(String relationName, List relations) {

        Relation relation = new Relation();
        List<Column> columns = new ArrayList<Column>();

        findMapColumns(relationName, relations, relation, columns);

        relation.setColumns(columns);
        return relation;
    }

    private void findMapColumns(String relationName, List<Map> relations, Relation relation, List<Column> columns) {


        relation.setName(relationName); //use a standard naming for sql can be written and passed into jimql
        //need to search through all the relations  since map may have null columns
        //can check this case later, just search through one for now

        //TODO - for now, need to loop through all data rows in case the map fields are different
        //this is not very efficient for a large data set. maybe allow an option to bypass


        for (Map row : relations) {

            Set<Map.Entry> entrySet = row.entrySet();


            for (Map.Entry entry : entrySet) {

                String propertyName = entry.getKey().toString();


                Column column = new Column();
                column.setColumnName(propertyName);
                if (!columns.contains(column)) {
                    if (entry.getValue() != null) {
                        Class<?> valueType = entry.getValue().getClass();
                        column.setMethodType(valueType);
                        //can have a null method type at the end. need to check
                    }

                    if (column.getMethodType() != null && (column.isSupportedType())) {
                        columns.add(column);
                    }
                }
            }
        }

    }


    protected List<Map<String, Object>> executeQuery(List<Relation> relations, String selectList, String criteria, Map<Integer,Object> parameters,String groupBy, String orderBy) {
        ResultSet rs = null;
        PreparedStatement ps = null;
        List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();


        try {
            StringBuilder sb = new StringBuilder();

            sb.append("select ").append(selectList).append(" from ");
            int i = 1;
            for (Relation relation : relations) {
                sb.append(relation.getName());
                if (i < relations.size()) {
                    sb.append(", ");
                }
                i++;
            }
            if (criteria != null && !criteria.isEmpty()) {
                sb.append(" WHERE ");
                sb.append(criteria);
            }


            if (groupBy != null && !groupBy.isEmpty()) {
                sb.append(" GROUP BY ");

                sb.append(groupBy);
            }
            if (orderBy != null && !orderBy.isEmpty()) {
                sb.append(" Order BY ");

                sb.append(orderBy);
            }

            ps = conn.prepareStatement(sb.toString());
            if (parameters!=null){
                          for (Map.Entry<Integer,Object> entry:parameters.entrySet()){
                              int pos = entry.getKey();
                              Object value = entry.getValue();
                              if (value instanceof  Integer){
                                    ps.setInt(pos,(Integer)value);
                              }

                              else  if (value instanceof  Long){
                                    ps.setLong(pos,(Long)value);
                              }

                              else  if (value instanceof  Double){
                                    ps.setDouble(pos,(Double)value);
                              }
                              else  if (value instanceof  BigDecimal){
                                     ps.setBigDecimal(pos,(BigDecimal)value);
                              }
                              else  if (value instanceof  Float){
                                   ps.setFloat(pos,(Float)value);
                              }
                              else  if (value instanceof  Boolean){
                                      ps.setBoolean(pos,(Boolean)value);
                              }
                              else  if (value instanceof  Date){
                                     ps.setDate(pos,new java.sql.Date(((Date)value).getTime()));
                              }
                               else  if (value instanceof  String){
                                     ps.setString(pos,(String)value);
                              }

                          }
                      }

            rs = ps.executeQuery();

            while (rs.next()) {
                Map<String, Object> map = new HashMap<String, Object>();
                int colCount = rs.getMetaData().getColumnCount();
                for (int j = 1; j <= colCount; j++) {
                    String columnName = rs.getMetaData().getColumnName(j);
                    if (!columnName.endsWith(HASHCODE_COL)) {
                        map.put(columnName, rs.getObject(j));
                    }
                }
                result.add(map);
            }

        } catch (SQLException e) {
            e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
        } finally {
            try {
                if (rs != null) {
                    rs.close();
                }
                if (ps != null) {
                    ps.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }


        }
        return result;

    }


    public List<Map<String, Object>> loadAndQuery(String selectList, String criteria, Map<Integer,Object> parameters,String groupBy, String orderBy, List... dataLists) {
        loadData(dataLists);
        return queryData(selectList, criteria, parameters,groupBy, orderBy, dataLists);

    }

    public List<Map<String, Object>> queryData(String selectList, String criteria, Map<Integer,Object> parameters,String groupBy, String orderBy, NamedList... namedLists) {
        List<Relation> relations = new ArrayList<Relation>();
        List<Map<String, Object>> result;



        for (NamedList namedList : namedLists) {
            String relationName = namedList.getListName();
            Relation relation = findColumns(relationName, namedList.getDataList());
            relations.add(relation);
        }
        createIndexes(relations, criteria, groupBy);

        result = executeQuery(relations, selectList, criteria,parameters, groupBy, orderBy);
        return result;
    }
    public List<Map<String, Object>> queryData(String selectList, String criteria,  String groupBy, String orderBy, List... dataLists) {
        return  queryData( selectList,  criteria,null,  groupBy,  orderBy,dataLists) ;
    }
    public List<Map<String, Object>> queryData(String selectList, String criteria, Map<Integer,Object> parameters, String groupBy, String orderBy, List... dataLists) {
        List<Relation> relations = new ArrayList<Relation>();
        List<Map<String, Object>> result;

        for (List dataList : dataLists) {
            String relationName = "tempmap" + Math.abs(dataList.get(0).hashCode());
            Relation relation = findColumns(relationName, dataList);
            relations.add(relation);
        }
        createIndexes(relations, criteria, groupBy);

        result = executeQuery(relations, selectList, criteria, parameters,groupBy, orderBy);
        return result;
    }


}
